package com.example.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

// 题目：三个线程交替打印A、B、C。然后打印总数到满100次之后停下来。
// 对应讲解此题的帖子地址：https://www.jianshu.com/p/7272c8e95df6
// 个人备注：本题主要考察的就是，线程与线程之间，如何协作。主要就是，如何让当前线程进入等待，如何在一个线程中唤醒某些处于等待的线程。
// 至于String CODE的状态怎么传递地，其实就是应为CODE定义在类中，而不是方法中，对于各个方法、各个线程来说相当于是全局变量，所以，CODE的状态改变就是这么在多个线程之间传递的。

public class MultithreadingAlternatingPrinting {
    // 先复习一下线程的状态相关的知识。
    // 线程有等待状态和可运行状态。当线程处于等待是，如果被其他线程唤醒，或者是自己等待超过等待时间了，该线程就又会进入等待状态。

    // 在java中，任何对象都可以作为Lock吗
    // 在 Java 中，任何对象都可以被用作锁，但并不是所有的对象都适合作为锁。在多线程编程中，通常使用以下几种对象作为锁：
    //1. **内置锁（Intrinsic Lock）：** 也称为监视器锁（Monitor Lock），是每个 Java 对象都自带的一种锁。通过 synchronized 关键字来使用内置锁，例如：`synchronized (obj) { ... }`。在 synchronized 块中，`obj` 就是用作锁的对象。
    //2. **Lock 接口的实现类对象：** Java 并发包提供了 `Lock` 接口及其实现类，例如 `ReentrantLock`、`ReentrantReadWriteLock` 等。这些实现类对象可以作为更灵活的锁，可以在更复杂的同步场景中使用。
    //3. **任意对象：** 除了以上两种情况，Java 中的任意对象都可以被用作锁。在这种情况下，我们使用对象的内置锁。例如，可以使用一个普通的对象来充当锁：`Object lock = new Object();`，然后通过 `synchronized (lock) { ... }` 来进行同步。
    //虽然任意对象都可以被用作锁，但是选择合适的对象作为锁很重要。通常情况下，应该选择在程序设计中就已经存在的对象，并且不容易被其他线程独占的对象。这样做可以减少竞争，提高程序的性能。

    // 虽然所有的对象都可以作为锁，但是呢，普通作为锁的时候，它的类型是Object的，而不是Lock类型的。并不是所有作为锁的对想都必然是Lock类型的。只有实现了Lock接口的实现类，它new出来的东西，才能用Lock接收。
    // ReentrantLock 是 Lock 接口的一个实现类，它允许重入性，即同一个线程可以多次获得同一个锁。

    // 创建一个锁，用来协调三个线程之间的动作。控制三个线程 ABC，保证同一时刻只有一个线程工作
    // new ReentrantLock();这个是创建一个可重入的锁。
    // new ReentrantLock(ture);是创建一个公平锁。
    // 此处之所以要用new ReentrantLock(true)来做为锁呢，我个人感觉，主要就是用公平锁，我个人感觉这个场景应该不是我们常讲的那种重入锁所针对的场景。因为一般来说，我们是会用一种常见的选择是使用公平锁，也是多用ReentrantLock类，并将其初始化为公平锁模式这种方式的。
    // 本例中，因为线程A、B、C这三个线程，它们要在同一个方法里面多次获取这个锁。但是我感觉不用可重入锁好像也没关系吧。因为，我们A、B、C不是嵌套的获得锁啊，它们下次获取锁之前，不是释放掉了锁吗。
    // 不用这个ReentrantLock会怎么样，我感觉好像也没太大的问题。
    private static Lock lock = new ReentrantLock(true);

    //  Condition ,控制等待或是 通知
    // private static Condition conditionA = lock.newCondition();: 这行代码创建了一个条件变量 conditionA。条件变量通常与锁一起使用，它提供了线程等待和唤醒的机制，以便线程可以在特定条件下等待或者唤醒其他线程。
    //lock.newCondition() 返回了与当前锁关联的新的条件对象。这意味着 conditionA 与之前定义的 lock 相关联，可以使用 conditionA 对象来进行线程等待和唤醒。
    private static Condition conditionA = lock.newCondition(); // Condition是与锁关联的。
    private static Condition conditionB = lock.newCondition();
    private static Condition conditionC = lock.newCondition();

    //默认的通知 暗号 A
    private static String CODE = "A";

    //设置初始打印值
    private static int COUNT_NUM = 1;

    //线程是否需要终止 标记
    private static volatile boolean IS_INTERRUPT = false;

    public static void main(String[] args) {

        Thread A = new Thread(() -> {
            while (!IS_INTERRUPT) {
                try {
                    printA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread B = new Thread(() -> {
            while (!IS_INTERRUPT) {
                try {
                    printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread C = new Thread(() -> {
            while (!IS_INTERRUPT) {
                try {
                    printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        A.start();
        B.start();
        C.start();
    }

    public static void printA() throws InterruptedException {

        //等待
        // lock.lock() 方法尝试获取锁。如果当前锁没有被其他线程持有，那么调用 lock.lock() 的线程将获取到这个锁，并继续执行后续的代码。
        // 如果锁已经被其他线程持有，那么调用 lock.lock() 的线程将被阻塞，直到获取到这个锁为止。被阻塞的线程将暂时停止执行，并进入等待状态，直到锁可用。
        lock.lock();
        try {
            if (COUNT_NUM >= 100) {
                IS_INTERRUPT = true;
                return;
            }
            //核对
            // CODE如果是B、C进来的话，会满足while循环条件，从而走conditionA.await();
            while (!CODE.equals("A")) {
                //暗号不对，就进入等待
                // conditionA.await();: 这行代码在当前线程上调用 await() 方法，导致当前线程进入等待状态，并释放与之关联的锁。
                // 线程会一直等待，直到其他线程调用与之关联的条件变量的 signal() 或 signalAll() 方法来唤醒它，或者被中断。
                //在这里，conditionA.await() 意味着当前线程在 conditionA 条件上等待，并且释放自己当前占用的锁。
                // 直到其他线程通过 conditionA.signal() 或 conditionA.signalAll() 来唤醒它，或者其他线程调用 conditionA.await() 的线程被中断。
                // 如果线程进入等待状态的话，那么，conditionA.await();这句话后面的其他代码都不会走了。
                conditionA.await();  // 方法printA、B、C都是线程任务里面的一部分。在线程要做的事情里面，一个线程对象使用一个condition就好。
            }
            System.out.println("A, count" + COUNT_NUM);
            //改暗号，通知B
            CODE = "B";
            COUNT_NUM = COUNT_NUM + 1;
            // conditionB.signalAll() 是 Java 中用于唤醒等待在指定条件上的所有线程的方法。让我来解释一下：
            //conditionB 是一个 Condition 对象，通常与某个锁（例如 ReentrantLock）相关联，用于控制线程的等待和唤醒。
            //signalAll() 方法用于唤醒等待在 conditionB 上的所有线程。这意味着所有在调用 conditionB.await() 方法等待的线程都会被唤醒，以便它们可以重新竞争锁并继续执行。
            //通常情况下，signalAll() 方法应该在一些状态发生变化时被调用，以通知所有等待线程，状态已经改变，它们可以检查新的状态并相应地执行操作。
            // 执行了这句之后，其他线程，有些之前是处于等待状态的，那么，被唤醒之后，就处于可执行状态。
            Thread.sleep(5000);
            conditionB.signalAll();
        // 一般情况下，为了避免死锁，获取锁后需要在适当的时候释放锁，以便其他线程可以获取到锁并继续执行。通常的做法是在 try-finally 块中获取和释放锁，确保即使在发生异常时也能正确释放锁。
        } finally {
            lock.unlock();
        }
    }

    public static void printB() throws InterruptedException {
        lock.lock();
        try {

            if (COUNT_NUM >= 100) {
                IS_INTERRUPT = true;
                return;
            }

            while (!CODE.equals("B")) {
                conditionB.await();
            }

            System.out.println("B, count" + COUNT_NUM);
            //改暗号，通知C
            CODE = "C";
            COUNT_NUM = COUNT_NUM + 1;
            Thread.sleep(5000);
            conditionC.signalAll();
        } finally {
            lock.unlock();
        }

    }

    public static void printC() throws InterruptedException {
        lock.lock();
        try {

            if (COUNT_NUM >= 100) {
                IS_INTERRUPT = true;
                return;
            }

            while (!CODE.equals("C")) {
                conditionC.await();
            }

            System.out.println("C, count" + COUNT_NUM);
            //改暗号，通知A
            CODE = "A";
            COUNT_NUM = COUNT_NUM + 1;
            Thread.sleep(5000);
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }

    }

}
